package net.openadventure;

import java.awt.Polygon;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import net.openadventure.animation.Animation;
import net.openadventure.animation.FrameLoop;
import net.openadventure.animation.OneTimeMove;
import net.openadventure.animation.WalkEgo;
import net.openadventure.hotspot.Hotspot;
import net.openadventure.hotspot.HotspotHandler;
import net.openadventure.location.Location;
import net.openadventure.sprites.Ego;
import net.openadventure.sprites.Sprite;
import util.Point;

/**
 * The Director works with Locations and runs LocationScripts. These actions are
 * executed and the Engine is manipulated with the results.
 * 
 * @author paul
 * 
 */
public class Director {

	private final Engine engine;

	private final State state = new State();

	public Director(Engine engine) {
		this.engine = engine;

		// TODO: test code

		// testHotspots();

		// testWalking();

		// testLoopAnimation();

		testInterlocationTravel();

	}

	public void updateScene(int mouseCoordinateX, int mouseCoordinateY) {
		// add components here

		// remove components here

		// Update all animation routines
		// System.out.println(state.getAnimations().size() + " animations.");
		for (Iterator<Animation> iter = state.getAnimations().values()
				.iterator(); iter.hasNext();) {
			Animation animation = iter.next();

			if (animation.finished(state)) {
				iter.remove();
			} else {
				animation.updateSprite(state);
			}
		}

		// Check for an ego moving command.
		if (state.getMouseContext() == MouseContext.WALK
				&& state.getLastMouseState() == MouseButtonState.MOUSE_RELEASED) {
			moveEgoTo(mouseCoordinateX, mouseCoordinateY);
		}

		// Check for hotspot activation
		performHotspotActivation(mouseCoordinateX, mouseCoordinateY);

		// set the lastMouseCoordinates
		state.setLastMouseCoordinateX(mouseCoordinateX);
		state.setLastMouseCoordinateY(mouseCoordinateY);

		state.setLastMouseState(engine.updateForeground(state.getSprites()));

		// Update the background (if needed)
		// engine.setBackgroundImage(background);
	}

	private void performHotspotActivation(int mouseCoordinateX,
			int mouseCoordinateY) {

		// This will eventually contain the complete script to execute
		ArrayList<String> script = new ArrayList<String>();

		// Check if the ego's footprint intersects a hotspot
		Ego ego = (Ego) state.getSprites().get("ego");
		Hotspot[] newActiveEgoHotspots = new Hotspot[0];
		if (ego != null) {
			newActiveEgoHotspots = state.getCurrentLocation()
					.checkPolygonForHotspots(ego.getActualFootPrint());
		}
		List<Hotspot> oldActiveEgoHotspots = state.getActiveEgoHotspots();
		// Check for ego entering or loitering in hotspots
		for (Hotspot hotspot : newActiveEgoHotspots) {
			if (oldActiveEgoHotspots.contains(hotspot)) {
				// Add loiter scriptlet to main script
				HotspotHandler handler = hotspot
						.getEgoHandler(EgoAction.LOITER);
				String[] scriptlet = handler.getScript();
				for (String line : scriptlet) {
					script.add(line);
				}
			} else {
				// Add enter scriptlet to main script
				HotspotHandler handler = hotspot.getEgoHandler(EgoAction.ENTER);
				String[] scriptlet = handler.getScript();
				for (String line : scriptlet) {
					script.add(line);
				}
				// Add new hotspot to the state's list
				oldActiveEgoHotspots.add(hotspot);
			}
		}
		// Check for ego exiting hotspots
		for (int i = 0; i < oldActiveEgoHotspots.size();) {
			Hotspot oldHotspot = oldActiveEgoHotspots.get(i);

			boolean foundInNew = false;
			for (Hotspot newHotspot : newActiveEgoHotspots) {
				if (newHotspot.equals(oldHotspot)) {
					foundInNew = true;
				}
			}

			if (!foundInNew) {
				// Add exit scriptlet to main script
				HotspotHandler handler = oldHotspot
						.getEgoHandler(EgoAction.EXIT);
				String[] scriptlet = handler.getScript();
				for (String line : scriptlet) {
					script.add(line);
				}
				// Remove old hotspot from the state's list
				oldActiveEgoHotspots.remove(oldHotspot);
			} else {
				i++;
			}
		}

		// Check for activation by the user
		Hotspot hotspot = state.getCurrentLocation()
				.checkCoordinatesForHotspot(mouseCoordinateX, mouseCoordinateY);

		if (hotspot != null) {

			HotspotHandler handler;

			// Check if the user clicked on the hotspot
			if (state.getLastMouseState() == MouseButtonState.MOUSE_RELEASED) {
				// check for correct handler
				handler = hotspot.getMouseHandler(MouseAction.CLICK, state
						.getMouseContext());

				if (handler != null) {
					// Add scriptlet to the complete script
					String[] scriptlet = handler.getScript();

					for (String line : scriptlet) {
						script.add(line);
					}
				}
			}

			// Check if the mouse is entering/hovering over the hotspot
			// Check history
			Hotspot previousHotspot = state.getCurrentLocation()
					.checkCoordinatesForHotspot(
							state.getLastMouseCoordinateX(),
							state.getLastMouseCoordinateY());
			if (previousHotspot != null && previousHotspot.equals(hotspot)) {
				// This is a hover
				// check for correct handler
				handler = hotspot.getMouseHandler(MouseAction.HOVER, state
						.getMouseContext());

				if (handler != null) {
					// Add scriptlet to the complete script
					String[] scriptlet = handler.getScript();

					for (String line : scriptlet) {
						script.add(line);
					}
				}
			} else {
				// This is an enter
				// check for correct handler
				handler = hotspot.getMouseHandler(MouseAction.ENTER, state
						.getMouseContext());

				if (handler != null) {
					// Add scriptlet to the complete script
					String[] scriptlet = handler.getScript();

					for (String line : scriptlet) {
						script.add(line);
					}
				}
			}

		}

		// Execute the complete script in the order in which it was received.
		performScriptedActions(ScriptParser.parseScript(script));
	}

	////////////////////////////////////////////////////////////////////////////
	// //////////////////
	// These methods handle script actions. Consider moving these to a separate
	// class/hierarchy
	private void performScriptedActions(String[] actions) {
		for (String action : actions) {
			String[] actionParts = action.split("[|]");
			if (actionParts[0].equalsIgnoreCase("print")) {
				printStringAction(actionParts[1]);
			} else if (actionParts[0].equalsIgnoreCase("addsprite")) {
				addSprite(actionParts[1], actionParts[2], Integer
						.parseInt(actionParts[3]), Integer
						.parseInt(actionParts[4]), Integer
						.parseInt(actionParts[5]), Integer
						.parseInt(actionParts[6]), Boolean
						.parseBoolean(actionParts[7]), Boolean
						.parseBoolean(actionParts[8]));
			} else if (actionParts[0].equalsIgnoreCase("removeallsprites")) {
				removeAllSprites();
			} else if (actionParts[0].equalsIgnoreCase("goto")) {
				goTo(actionParts[1]);
			} else if (actionParts[0].equalsIgnoreCase("loopsprite")) {
				// split comma separated list into string array
				String[] list = actionParts[2].split(",");

				loopSprite(actionParts[1], list, Double
						.parseDouble(actionParts[3]));
			}
		}
	}

	private void printStringAction(String string) {
		System.out.println(string);
	}

	private void addSprite(String name, String imagePath, int x, int y,
			int width, int height, boolean flipHorizontal, boolean flipVertical) {
		state.getSprites().put(
				name,
				new Sprite(imagePath, x, y, null, null, width, height,
						flipHorizontal, flipVertical));
	}

	private void removeAllSprites() {
		state.getSprites().clear();
	}

	private void goTo(String locationName) {
		state.setCurrentLocation(state.getLocation(locationName));

		// This should be done here rather than during update because it should
		// supersede all user interaction.
		engine.setBackgroundImage(state.getCurrentLocation()
				.getActiveBackgroundImagePath());

		// Execute any "onLoad" scripts for this location
		String[] lines = state.getCurrentLocation().getOnLoadScript();
		ArrayList<String> script = new ArrayList<String>();
		for (String line : lines) {
			script.add(line);
		}
		performScriptedActions(ScriptParser.parseScript(script));
	}

	private void moveEgoTo(int mouseCoordinateX, int mouseCoordinateY) {
		state.getAnimations().put("moveEgo",
				new WalkEgo(3, mouseCoordinateX, mouseCoordinateY, state));
	}

	private void moveSpriteTo(String spriteName, int toX, int toY) {
		state.getAnimations().put("moveSpriteTo",
				new OneTimeMove(spriteName, 1, 0, 0, toX, toY));
	}

	private void loopSprite(String spriteName, String[] imagePaths,
			double framesPerUpdate) {

		state.getAnimations().put("loopSprite",
				new FrameLoop(spriteName, imagePaths, framesPerUpdate));
	}

	////////////////////////////////////////////////////////////////////////////
	// //////////////////

	// These methods contain test code.
	@SuppressWarnings("unused")
	private void testHotspots() {
		// LookoutDeck1
		Location lookoutDeck1 = new Location("Lookout Deck1");
		lookoutDeck1.addBackgroundImagePath("day", "images/deck.jpg");
		Hotspot ld1Hotspot = new Hotspot();
		ld1Hotspot.setArea(new Polygon(new int[] { 0, 100, 100, 0 }, new int[] {
				0, 0, 100, 100 }, 4));
		ld1Hotspot
				.setMouseHandler(
						MouseAction.CLICK,
						MouseContext.ACTION,
						new HotspotHandler(
								"goto lookout2\naddsprite snail images/snail.gif 100 200 150 155 false false"));
		ld1Hotspot.setMouseHandler(MouseAction.ENTER, MouseContext.ACTION,
				new HotspotHandler("print Click Me!"));
		lookoutDeck1.addHotspot("box", ld1Hotspot);
		state.addLocation("lookout1", lookoutDeck1);

		// LookoutDeck2
		Location lookoutDeck2 = new Location("Lookout Deck2");
		lookoutDeck2.addBackgroundImagePath("day", "images/deck2.jpg");
		Hotspot ld2Hotspot = new Hotspot();
		ld2Hotspot.setArea(new Polygon(new int[] { 0, 100, 100, 0 }, new int[] {
				0, 0, 100, 100 }, 4));
		ld2Hotspot.setMouseHandler(MouseAction.CLICK, MouseContext.ACTION,
				new HotspotHandler("removeAllSprites\ngoto lookout1"));
		lookoutDeck2.addHotspot("box", ld2Hotspot);
		state.addLocation("lookout2", lookoutDeck2);

		state.setCurrentLocation(lookoutDeck1);

		// set the background
		engine.setBackgroundImage(state.getCurrentLocation()
				.getActiveBackgroundImagePath());
	}

	@SuppressWarnings("unused")
	private void testWalking() {
		// LookoutDeck1
		Location livingRoom = new Location("Livingroom");
		livingRoom.addBackgroundImagePath("day", "images/funnyroom.jpg");

		WalkableArea walkable = new WalkableArea();
		walkable.setPolygon(new Polygon(new int[] { 100, 540, 590, 50 },
				new int[] { 250, 250, 430, 430 }, 4));

		livingRoom.addWalkableArea("main", walkable);

		Ego ego = new Ego("images/funnyman.png", 200, 300, new Point(25, 97),
				new Polygon(new int[] { 0, 50, 50, 0 }, new int[] { 90, 90,
						100, 100 }, 4), 50, 100);
		// sprites.add(ego);
		state.getSprites().put("ego", ego);
		//		
		// state.setEgo(ego);
		state.addLocation("lookout1", livingRoom);
		state.setCurrentLocation(livingRoom);
		state.setMouseContext(MouseContext.WALK);

		// set the background
		engine.setBackgroundImage(state.getCurrentLocation()
				.getActiveBackgroundImagePath());
	}

	private void testLoopAnimation() {
		// LookoutDeck1
		Location livingRoom = new Location("Livingroom");
		livingRoom.addBackgroundImagePath("default", "images/funnyroom.jpg");

		WalkableArea walkable = new WalkableArea();
		walkable.setPolygon(new Polygon(new int[] { 100, 540, 590, 50 },
				new int[] { 250, 250, 430, 430 }, 4));

		livingRoom.addWalkableArea("main", walkable);
		livingRoom
				.setOnLoadScript("addsprite clock images/clock1.gif 450 60 50 50 false false\n"
						+ "loopsprite clock images/clock1.gif,images/clock3.gif,images/clock2.gif,"
						+ "images/clock4.gif 0.04");

		// Test ego hotspot activations
		Hotspot warningRectangle = new Hotspot();
		warningRectangle.setArea(new Polygon(new int[] { 300, 340, 340, 300 },
				new int[] { 320, 320, 390, 390 }, 4));
		warningRectangle.setEgoHandler(EgoAction.ENTER, new HotspotHandler(
				"print Ego Entered"));
		warningRectangle.setEgoHandler(EgoAction.LOITER, new HotspotHandler(
				"print Ego Loitering"));
		warningRectangle.setEgoHandler(EgoAction.EXIT, new HotspotHandler(
				"print Ego Exited"));
		livingRoom.addHotspot("warningRectangle", warningRectangle);

		Ego ego = new Ego("images/funnyman.png", 200, 300, new Point(25, 97),
				new Polygon(new int[] { 0, 50, 50, 0 }, new int[] { 90, 90,
						100, 100 }, 4), 50, 100);

		// Add obstacle sprite
		Sprite snail = new Sprite("images/snail.gif", 450, 300, new Point(50,
				50), new Polygon(new int[] { 0, 50, 50, 0 }, new int[] { 0, 0,
				50, 50 }, 4), 50, 50, false, false);

		state.getSprites().put("snail", snail);

		state.getSprites().put("ego", ego);

		state.addLocation("livingroom", livingRoom);
		state.setCurrentLocation(livingRoom);
		state.setMouseContext(MouseContext.WALK);

		moveSpriteTo("snail", 200, 400);
		goTo("livingroom");
	}

	private void testInterlocationTravel() {
		// livingRoom1
		Location livingRoom1 = new Location("Livingroom1");
		livingRoom1.addBackgroundImagePath("default", "images/funnyroom.jpg");

		WalkableArea walkable = new WalkableArea();
		walkable.setPolygon(new Polygon(new int[] { 100, 540, 590, 50 },
				new int[] { 250, 250, 430, 430 }, 4));

		livingRoom1.addWalkableArea("main", walkable);
		livingRoom1
				.setOnLoadScript("addsprite clock images/clock1.gif 450 60 50 50 false false\n"
						+ "loopsprite clock images/clock1.gif,images/clock3.gif,images/clock2.gif,"
						+ "images/clock4.gif 0.04");

		// Test ego hotspot activations
		Hotspot warningRectangle = new Hotspot();
		warningRectangle.setArea(new Polygon(new int[] { 300, 340, 340, 300 },
				new int[] { 320, 320, 390, 390 }, 4));
		warningRectangle.setEgoHandler(EgoAction.ENTER, new HotspotHandler(
				"print Ego Entered"));
		warningRectangle.setEgoHandler(EgoAction.LOITER, new HotspotHandler(
				"print Ego Loitering"));
		warningRectangle.setEgoHandler(EgoAction.EXIT, new HotspotHandler(
				"print Ego Exited"));
		livingRoom1.addHotspot("warningRectangle", warningRectangle);

		// Test location change ego hotspot activation
		Hotspot room1RightDoor = new Hotspot();
		room1RightDoor.setArea(new Polygon(new int[] { 530, 550, 570, 550 },
				new int[] { 300, 300, 370, 370 }, 4));
		room1RightDoor.setEgoHandler(EgoAction.ENTER, new HotspotHandler(
				"goto livingroom2"));
		livingRoom1.addHotspot("rightDoor", room1RightDoor);

		// livingroom2
		Location livingRoom2 = new Location("Livingroom2");
		livingRoom2.addBackgroundImagePath("default", "images/funnyroom2.jpg");
		livingRoom2.addWalkableArea("main", walkable);

		Hotspot room2LeftDoor = new Hotspot();
		room2LeftDoor.setArea(new Polygon(new int[] { 90, 110, 90, 70 },
				new int[] { 300, 300, 370, 370 }, 4));
		room2LeftDoor.setEgoHandler(EgoAction.ENTER, new HotspotHandler(
				"goto livingroom1"));
		livingRoom2.addHotspot("leftDoor", room2LeftDoor);

		Ego ego = new Ego("images/funnyman.png", 200, 300, new Point(25, 97),
				new Polygon(new int[] { 0, 50, 50, 0 }, new int[] { 90, 90,
						100, 100 }, 4), 50, 100);

		// Add obstacle sprite
		Sprite snail = new Sprite("images/snail.gif", 450, 300, new Point(50,
				50), new Polygon(new int[] { 0, 50, 50, 0 }, new int[] { 0, 0,
				50, 50 }, 4), 50, 50, false, false);

		state.getSprites().put("snail", snail);

		state.getSprites().put("ego", ego);

		state.addLocation("livingroom1", livingRoom1);
		state.addLocation("livingroom2", livingRoom2);
		state.setCurrentLocation(livingRoom1);
		state.setMouseContext(MouseContext.WALK);

		moveSpriteTo("snail", 200, 400);
		goTo("livingroom1");
	}
}
